package appli.agentImpl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import system.agent.Agent;
import system.agent.CommunicationAgentInterface;
import system.util.Server;
import system.util.message.Message;
import system.util.message.RequestMessage;
import system.util.message.ResponseMessage;
import appli.product.Product;
import appli.product.ProductManager;
import appli.product.ProductOrder;

/**
 * A Buyer is an agent who go all over stores to buy a list of products
 * at the best price.
 */
public class Buyer extends Agent{

	private static final long serialVersionUID = -4999319755083468154L;

	private enum State {Initial, CheckingDirectory, CheckingBestPrices, BuyingProducts, Finished};

	/**
	 * List of the stores to visit (<storeName, storeAdresse>)
	 */
	private Map<String, String> stores;

	/**
	 * Stores already visited 
	 */
	private List<String> storesToVisit;

	/**
	 * Store currently being visited or null if no store is curently being visited.
	 */
	private String currentStoreName;

	/**
	 * Manager of the products to buy
	 */
	private ProductManager productManager;

	/**
	 * Current state of the agent
	 */
	private State currentState = State.Initial;

	/**
	 * Products already bought
	 * <Product, StoreName>
	 */
	private Map<Product, String> boughtProducts;
	

	public Buyer(String agentName, String host, int port, List<ProductOrder> productToBuy) {
		super(agentName, host, port);
		this.productManager = new ProductManager(productToBuy);
	}

	public void setStores(Map<String, String> stores){
		this.stores = stores;
	}

	@Override
	public void main() {
		String msg;
		RequestMessage reqMsg;
		ResponseMessage respMsg;
		switch(currentState){
		case Initial:
			// move to the directory in order to gather store's adresses
			if (OPTION) { 
				System.out.println("Acheteur " + getAgentName() + " : part visiter l'annuaire");
			}
			this.currentState =  State.CheckingDirectory;
			move(Directory.directoryAddressHost, Directory.directoryAddressPort);
			break;
		case CheckingDirectory:
			// ask to the directory to gather the store's adresses
			if (OPTION) { 
				System.out.println("Acheteur " + getAgentName() + " : Demande des adresses des magasins");
			}
			CommunicationAgentInterface directory = Server.getAgent(Directory.directoryName);
			msg = "GET" + Message.SEPARATOR + "STORES" + Message.SEPARATOR + "MAP";
			reqMsg = new RequestMessage(this.getAgentName(), msg);
			respMsg = reqMsg.getFuture();
			directory.handleMessage(reqMsg);
			respMsg.waitForMessage();
			messageTraitment(respMsg);
			this.currentState = State.CheckingBestPrices;
			this.storesToVisit = new ArrayList<String>();
			for(String store : stores.keySet()) {
				storesToVisit.add(store);
			}
			visitAnotherStore();
			break;
		case CheckingBestPrices:
			// visit all stores to find the best prices of the products to buy
			if (OPTION) { 
				System.out.println("Acheteur " + getAgentName() + " : Demande des prix des produits du magasin " + currentStoreName);
			}
			CommunicationAgentInterface store = Server.getAgent(currentStoreName);
			msg = "GET" + Message.SEPARATOR + "PRODUCTS" + Message.SEPARATOR + "LIST";
			reqMsg = new RequestMessage(this.getAgentName(), msg);
			respMsg = reqMsg.getFuture();
			store.handleMessage(reqMsg);
			respMsg.waitForMessage();
			messageTraitment(respMsg);
			if(storesToVisit.size() > 0){
				//visit another store
				visitAnotherStore();
			}
			else {
				if (OPTION) { 
					System.out.println("Acheteur " + getAgentName() + " : Tout les magasins ont étés visités, début des achats");
				}
				// all stores has been visited
				storesToVisit = productManager.buyingProductsMode();
				currentState = State.BuyingProducts;
				boughtProducts = new HashMap<Product, String>();
				visitAnotherStore();
			}
			break;
		case BuyingProducts:
			// visit the best stores and buy the products then go back at the initial place
			List<ProductOrder> productsToBuy = productManager.getProductsToBuy(currentStoreName);
			CommunicationAgentInterface storeAgent = Server.getAgent(currentStoreName);
			List<ResponseMessage> futures = new ArrayList<ResponseMessage>();
			int nbWaitingOrders = 0;
			for(ProductOrder po : productsToBuy){
				msg = "BUY" + Message.SEPARATOR +  po.productName + Message.SEPARATOR + po.quantity;
				reqMsg = new RequestMessage(this.getAgentName(), msg);
				futures.add(reqMsg.getFuture());
				storeAgent.handleMessage(reqMsg);
				nbWaitingOrders++;
			}
			for(int i = 0; i < nbWaitingOrders; i++){
				futures.get(i).waitForMessage();
				messageTraitment(futures.get(i));				
			}
			if(storesToVisit.size() > 0)
				// go to another store
				visitAnotherStore();
			else{
				// all stores has been visited
				currentState = State.Finished;
				this.back();
			}
			break;
		case Finished:
			if (OPTION) { 
				System.out.println("Acheteur " + getAgentName()  + " : Achats terminés, de retour à la place initiale");
				System.out.println("Les achats sont : ");
				for(Entry<Product, String> e : boughtProducts.entrySet()){
					Product p = e.getKey();
					System.out.println("\t" + p.getQuantity() + " " + p.getName() + " acheté(s) au magasin " + e.getValue() + " au prix de " + p.getPrice() + " euro(s) l'unité");
				}
			}
			break;
		}
	}

	/**
	 * Move to another store
	 */
	private void visitAnotherStore(){
		if(storesToVisit.size() > 0){
			String store = storesToVisit.remove(0);
			currentStoreName = store;
			String[] adress = stores.get(store).split(":");
			move(adress[0], Integer.parseInt(adress[1]));
		}
	}

	/**
	 * Treatment of the incoming messages
	 * @param m Message to deal with
	 */
	@SuppressWarnings("unchecked")
	private void messageTraitment(Message m){
		try {
			String[] msg = m.getMessage();
			if(msg[0].equals("SET")){
				if(msg[1].equals("STORES")){
					if(msg[2].equals("MAP")){
						Map<String, String> stores = (Map<String, String>)(m.getData());
						setStores(stores);
					}
				}
				else if(msg[1].equals("PRODUCTS")){
					if(msg[2].equals("LIST")){
						List<Product> products = (List<Product>)(m.getData());
						for(Product product : products)
							productManager.add(currentStoreName, product);
					}
				}
			}
			else if(msg[0].equals("BUY OK")) {
				Product p = (Product)(m.getData());
					boughtProducts.put(p, currentStoreName);
				if (OPTION) { 
					System.out.println("Acheteur " + getAgentName() + " : Achat de " + p.getQuantity() 
							+ " " + p.getName() + " au magasin " + currentStoreName + " au prix de " + p.getPrice() + " euro(s) l'unité");
				}
			}
		}
		catch (IndexOutOfBoundsException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		// test de ProductManager
		List<ProductOrder> lpo = new ArrayList<ProductOrder>();
		lpo.add(new ProductOrder("cornichon", 8));
		lpo.add(new ProductOrder("pattate", 2));
		lpo.add(new ProductOrder("poivron", 7));
		Buyer buyer = new Buyer("youhou", "host", 1987, lpo);
		buyer.productManager.add("chezPepe", new Product("cornichon", 4, 15));
		buyer.productManager.add("chezTati", new Product("cornichon", 4, 10));
		buyer.productManager.add("chezPinpin", new Product("cornichon", 7, 10));
		System.out.println(buyer.productManager);
	}
}
